Micron Document
🎖️GitЯра🎖️

Commit 174315b21f5affcb952642f1650d316c86b8a40f


Parents : 172680f
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-04-11T18:39:29-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-04-11T23:39:29Z

refactor(data): replace lateinit var scope + start() with constructor injection (#5075)

Changes

43 files changed, 188 insertions(+), 301 deletions(-)


Diff

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/CommandSenderImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/CommandSenderImpl.kt
index c26dc0f5f8..ca22f927d7 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/CommandSenderImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/CommandSenderImpl.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import okio.ByteString
import okio.ByteString.Companion.toByteString
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DataPacket
@@ -59,8 +60,8 @@ class CommandSenderImpl(
private val radioConfigRepository: RadioConfigRepository,
private val tracerouteHandler: TracerouteHandler,
private val neighborInfoHandler: NeighborInfoHandler,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : CommandSender {
- private lateinit var scope: CoroutineScope
private val currentPacketId = atomic(Random(nowMillis).nextLong().absoluteValue)
private val sessionPasskey = atomic(ByteString.EMPTY)
@@ -71,8 +72,7 @@ class CommandSenderImpl(
// maybe via ServiceRepository or similar.
// For now I'll assume it's injected or available.
- override fun start(scope: CoroutineScope) {
- this.scope = scope
+ init {
radioConfigRepository.localConfigFlow.onEach { localConfig.value = it }.launchIn(scope)
radioConfigRepository.channelSetFlow.onEach { channelSet.value = it }.launchIn(scope)
}

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImpl.kt
index 027947453c..7f9e6c3fab 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImpl.kt
@@ -19,6 +19,7 @@ package org.meshtastic.core.data.manager
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import okio.ByteString.Companion.toByteString
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.database.DatabaseManager
import org.meshtastic.core.common.util.handledLaunch
@@ -64,12 +65,8 @@ class MeshActionHandlerImpl(
private val notificationManager: NotificationManager,
private val messageProcessor: Lazy<MeshMessageProcessor>,
private val radioConfigRepository: RadioConfigRepository,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshActionHandler {
- private lateinit var scope: CoroutineScope
-
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
companion object {
private const val DEFAULT_REBOOT_DELAY = 5

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt
index dc544a300c..b7b27aa4e2 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt
@@ -21,6 +21,7 @@ import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import okio.IOException
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.model.ConnectionState
@@ -56,17 +57,13 @@ class MeshConfigFlowManagerImpl(
private val analytics: PlatformAnalytics,
private val commandSender: CommandSender,
private val packetHandler: PacketHandler,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshConfigFlowManager {
- private lateinit var scope: CoroutineScope
private val wantConfigDelay = 100L
/** Monotonically increasing generation so async clears from a stale handshake are discarded. */
private val handshakeGeneration = atomic(0L)
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
-
/**
* Type-safe handshake state machine. Each state carries exactly the data that is valid during that phase,
* eliminating the possibility of accessing stale or uninitialized fields.

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt
index 06d9732046..b622cedbf2 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt
@@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.repository.MeshConfigHandler
@@ -40,8 +41,8 @@ class MeshConfigHandlerImpl(
private val radioConfigRepository: RadioConfigRepository,
private val serviceRepository: ServiceRepository,
private val nodeManager: NodeManager,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshConfigHandler {
- private lateinit var scope: CoroutineScope
private val _localConfig = MutableStateFlow(LocalConfig())
override val localConfig = _localConfig.asStateFlow()
@@ -49,8 +50,7 @@ class MeshConfigHandlerImpl(
private val _moduleConfig = MutableStateFlow(LocalModuleConfig())
override val moduleConfig = _moduleConfig.asStateFlow()
- override fun start(scope: CoroutineScope) {
- this.scope = scope
+ init {
radioConfigRepository.localConfigFlow.onEach { _localConfig.value = it }.launchIn(scope)
radioConfigRepository.moduleConfigFlow.onEach { _moduleConfig.value = it }.launchIn(scope)
}

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt
index 5954b579c8..fde8841ceb 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt
@@ -19,13 +19,13 @@ package org.meshtastic.core.data.manager
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.common.util.nowMillis
@@ -81,17 +81,15 @@ class MeshConnectionManagerImpl(
private val packetRepository: PacketRepository,
private val workerManager: MeshWorkerManager,
private val appWidgetUpdater: AppWidgetUpdater,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshConnectionManager {
- private lateinit var scope: CoroutineScope
private var sleepTimeout: Job? = null
private var locationRequestsJob: Job? = null
private var handshakeTimeout: Job? = null
private var connectTimeMsec = 0L
private var connectionRestored = false
- @OptIn(FlowPreview::class)
- override fun start(scope: CoroutineScope) {
- this.scope = scope
+ init {
radioInterfaceService.connectionState.onEach(::onRadioConnectionState).launchIn(scope)
// Ensure notification title and content stay in sync with state changes
@@ -302,8 +300,7 @@ class MeshConnectionManagerImpl(
// Start MQTT if enabled
scope.handledLaunch {
val moduleConfig = radioConfigRepository.moduleConfigFlow.first()
- mqttManager.start(
- scope,
+ mqttManager.startProxy(
moduleConfig.mqtt?.enabled == true,
moduleConfig.mqtt?.proxy_to_client_enabled == true,
)

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt
index 07521b21c3..5da0448b5f 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt
@@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.common.util.nowMillis
@@ -94,14 +95,8 @@ class MeshDataHandlerImpl(
private val storeForwardHandler: StoreForwardPacketHandler,
private val telemetryHandler: TelemetryPacketHandler,
private val adminPacketHandler: AdminPacketHandler,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshDataHandler {
- private lateinit var scope: CoroutineScope
-
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- storeForwardHandler.start(scope)
- telemetryHandler.start(scope)
- }
private val rememberDataType =
setOf(

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
index 9fd28ecb4e..288ae9645b 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.common.util.nowMillis
@@ -53,8 +54,8 @@ class MeshMessageProcessorImpl(
private val meshLogRepository: Lazy<MeshLogRepository>,
private val router: Lazy<MeshRouter>,
private val fromRadioDispatcher: FromRadioPacketHandler,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MeshMessageProcessor {
- private lateinit var scope: CoroutineScope
private val mapsMutex = Mutex()
private val logUuidByPacketId = mutableMapOf<Int, String>()
@@ -75,8 +76,7 @@ class MeshMessageProcessorImpl(
scope.launch { earlyMutex.withLock { earlyReceivedPackets.clear() } }
}
- override fun start(scope: CoroutineScope) {
- this.scope = scope
+ init {
nodeManager.isNodeDbReady
.onEach { ready ->
if (ready) {

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshRouterImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshRouterImpl.kt
index aaf109be9c..8973589bde 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshRouterImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshRouterImpl.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.data.manager
-import kotlinx.coroutines.CoroutineScope
import org.koin.core.annotation.Single
import org.meshtastic.core.repository.MeshActionHandler
import org.meshtastic.core.repository.MeshConfigFlowManager
@@ -64,13 +63,4 @@ class MeshRouterImpl(
override val xmodemManager: XModemManager
get() = xmodemManagerLazy.value
-
- override fun start(scope: CoroutineScope) {
- dataHandler.start(scope)
- configHandler.start(scope)
- tracerouteHandler.start(scope)
- neighborInfoHandler.start(scope)
- configFlowManager.start(scope)
- actionHandler.start(scope)
- }
}

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt
index 969b67a2fd..b928e85052 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt
@@ -23,6 +23,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.network.repository.MQTTRepository
import org.meshtastic.core.repository.MqttManager
@@ -36,12 +37,11 @@ class MqttManagerImpl(
private val mqttRepository: MQTTRepository,
private val packetHandler: PacketHandler,
private val serviceRepository: ServiceRepository,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : MqttManager {
- private lateinit var scope: CoroutineScope
private var mqttMessageFlow: Job? = null
- override fun start(scope: CoroutineScope, enabled: Boolean, proxyToClientEnabled: Boolean) {
- this.scope = scope
+ override fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean) {
if (mqttMessageFlow?.isActive == true) return
if (enabled && proxyToClientEnabled) {
mqttMessageFlow =

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt
index 4019e5a9b4..3f483ba254 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt
@@ -20,7 +20,6 @@ import co.touchlab.kermit.Logger
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.update
import kotlinx.collections.immutable.persistentMapOf
-import kotlinx.coroutines.CoroutineScope
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.common.util.nowMillis
@@ -37,16 +36,11 @@ class NeighborInfoHandlerImpl(
private val serviceRepository: ServiceRepository,
private val serviceBroadcasts: ServiceBroadcasts,
) : NeighborInfoHandler {
- private lateinit var scope: CoroutineScope
private val startTimes = atomic(persistentMapOf<Int, Long>())
override var lastNeighborInfo: NeighborInfo? = null
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
-
override fun recordStartTime(requestId: Int) {
startTimes.update { it.put(requestId, nowMillis) }
}

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt
index 9ce4ba05d7..fe6d22f4cc 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import okio.ByteString
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.model.DataPacket
@@ -59,8 +60,8 @@ class NodeManagerImpl(
private val nodeRepository: NodeRepository,
private val serviceBroadcasts: ServiceBroadcasts,
private val notificationManager: NotificationManager,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : NodeManager {
- private lateinit var scope: CoroutineScope
private val _nodeDBbyNodeNum = atomic(persistentMapOf<Int, Node>())
private val _nodeDBbyID = atomic(persistentMapOf<String, Node>())
@@ -88,10 +89,6 @@ class NodeManagerImpl(
myNodeNum.value = num
}
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
-
companion object {
private const val TIME_MS_TO_S = 1000L
}

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.kt
index 1d4d11adc5..7c634ee8b0 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.kt
@@ -28,6 +28,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.common.util.nowMillis
@@ -60,6 +61,7 @@ class PacketHandlerImpl(
private val radioInterfaceService: RadioInterfaceService,
private val meshLogRepository: Lazy<MeshLogRepository>,
private val serviceRepository: ServiceRepository,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : PacketHandler {
companion object {
@@ -67,7 +69,6 @@ class PacketHandlerImpl(
}
private var queueJob: Job? = null
- private lateinit var scope: CoroutineScope
private val queueMutex = Mutex()
private val queuedPackets = mutableListOf<MeshPacket>()
@@ -79,11 +80,6 @@ class PacketHandlerImpl(
private val responseMutex = Mutex()
private val queueResponse = mutableMapOf<Int, CompletableDeferred<Boolean>>()
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- queueStopped = false // Safe: called before any concurrent operations on this scope.
- }
-
override fun sendToRadio(p: ToRadio) {
Logger.d { "Sending to radio ${p.toPIIString()}" }
val b = p.encode()

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt
index 4f71879ce1..e8ab4eeb72 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt
@@ -20,6 +20,7 @@ import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import okio.ByteString.Companion.toByteString
import okio.IOException
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.model.DataPacket
@@ -45,12 +46,8 @@ class StoreForwardPacketHandlerImpl(
private val serviceBroadcasts: ServiceBroadcasts,
private val historyManager: HistoryManager,
private val dataHandler: Lazy<MeshDataHandler>,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : StoreForwardPacketHandler {
- private lateinit var scope: CoroutineScope
-
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
override fun handleStoreAndForward(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) {
val payload = packet.decoded?.payload ?: return

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt
index 205dd30e2d..4887ff19b6 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt
@@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.model.DataPacket
@@ -49,16 +50,12 @@ class TelemetryPacketHandlerImpl(
private val nodeManager: NodeManager,
private val connectionManager: Lazy<MeshConnectionManager>,
private val notificationManager: NotificationManager,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : TelemetryPacketHandler {
- private lateinit var scope: CoroutineScope
private val batteryMutex = Mutex()
private val batteryPercentCooldowns = mutableMapOf<Int, Long>()
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
-
@Suppress("LongMethod", "CyclomaticComplexMethod")
override fun handleTelemetry(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) {
val payload = packet.decoded?.payload ?: return

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt
index 5e8d954f67..a5997208bd 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt
@@ -22,6 +22,7 @@ import kotlinx.atomicfu.update
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.common.util.handledLaunch
@@ -42,15 +43,11 @@ class TracerouteHandlerImpl(
private val serviceRepository: ServiceRepository,
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository,
private val nodeRepository: NodeRepository,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) : TracerouteHandler {
- private lateinit var scope: CoroutineScope
private val startTimes = atomic(persistentMapOf<Int, Long>())
- override fun start(scope: CoroutineScope) {
- this.scope = scope
- }
-
override fun recordStartTime(requestId: Int) {
startTimes.update { it.put(requestId, nowMillis) }
}

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImplTest.kt
index 6ac094e48b..c53c2577e0 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshActionHandlerImplTest.kt
@@ -25,6 +25,7 @@ import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verify.VerifyMode.Companion.not
import dev.mokkery.verifySuspend
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -89,28 +90,28 @@ class MeshActionHandlerImplTest {
every { nodeManager.myNodeNum } returns myNodeNumFlow
every { nodeManager.getMyId() } returns "!12345678"
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
-
- handler =
- MeshActionHandlerImpl(
- nodeManager = nodeManager,
- commandSender = commandSender,
- packetRepository = lazy { packetRepository },
- serviceBroadcasts = serviceBroadcasts,
- dataHandler = lazy { dataHandler },
- analytics = analytics,
- meshPrefs = meshPrefs,
- databaseManager = databaseManager,
- notificationManager = notificationManager,
- messageProcessor = lazy { messageProcessor },
- radioConfigRepository = radioConfigRepository,
- )
}
+ private fun createHandler(scope: CoroutineScope): MeshActionHandlerImpl = MeshActionHandlerImpl(
+ nodeManager = nodeManager,
+ commandSender = commandSender,
+ packetRepository = lazy { packetRepository },
+ serviceBroadcasts = serviceBroadcasts,
+ dataHandler = lazy { dataHandler },
+ analytics = analytics,
+ meshPrefs = meshPrefs,
+ databaseManager = databaseManager,
+ notificationManager = notificationManager,
+ messageProcessor = lazy { messageProcessor },
+ radioConfigRepository = radioConfigRepository,
+ scope = scope,
+ )
+
// ---- handleUpdateLastAddress (device-switch path — P0 critical) ----
@Test
fun handleUpdateLastAddress_differentAddress_switchesDatabaseAndClearsState() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { meshPrefs.deviceAddress } returns MutableStateFlow("old_addr")
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
@@ -128,7 +129,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleUpdateLastAddress_sameAddress_noOp() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { meshPrefs.deviceAddress } returns MutableStateFlow("same_addr")
@@ -141,7 +142,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleUpdateLastAddress_nullAddress_switchesIfDifferent() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { meshPrefs.deviceAddress } returns MutableStateFlow("old_addr")
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
@@ -156,7 +157,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleUpdateLastAddress_nullToNull_noOp() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { meshPrefs.deviceAddress } returns MutableStateFlow(null)
@@ -168,7 +169,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleUpdateLastAddress_executesStepsInOrder() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { meshPrefs.deviceAddress } returns MutableStateFlow("old")
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
@@ -187,7 +188,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_nullMyNodeNum_doesNothing() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
myNodeNumFlow.value = null
val node = createTestNode(REMOTE_NODE_NUM)
@@ -201,7 +202,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_favorite_sendsSetFavoriteWhenNotFavorite() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val node = createTestNode(REMOTE_NODE_NUM, isFavorite = false)
handler.onServiceAction(ServiceAction.Favorite(node))
@@ -213,7 +214,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_favorite_sendsRemoveFavoriteWhenAlreadyFavorite() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val node = createTestNode(REMOTE_NODE_NUM, isFavorite = true)
handler.onServiceAction(ServiceAction.Favorite(node))
@@ -227,7 +228,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_ignore_togglesAndUpdatesFilteredBySender() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val node = createTestNode(REMOTE_NODE_NUM, isIgnored = false)
handler.onServiceAction(ServiceAction.Ignore(node))
@@ -242,7 +243,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_mute_togglesMutedState() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val node = createTestNode(REMOTE_NODE_NUM, isMuted = false)
handler.onServiceAction(ServiceAction.Mute(node))
@@ -256,7 +257,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_getDeviceMetadata_sendsAdminRequest() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
handler.onServiceAction(ServiceAction.GetDeviceMetadata(REMOTE_NODE_NUM))
advanceUntilIdle()
@@ -268,7 +269,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_sendContact_completesWithTrueOnSuccess() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
everySuspend { commandSender.sendAdminAwait(any(), any(), any(), any()) } returns true
val action = ServiceAction.SendContact(SharedContact())
@@ -281,7 +282,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_sendContact_completesWithFalseOnFailure() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
everySuspend { commandSender.sendAdminAwait(any(), any(), any(), any()) } returns false
val action = ServiceAction.SendContact(SharedContact())
@@ -296,7 +297,7 @@ class MeshActionHandlerImplTest {
@Test
fun onServiceAction_importContact_sendsAdminAndUpdatesNode() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val contact =
SharedContact(node_num = REMOTE_NODE_NUM, user = User(id = "!abcdef12", long_name = "TestUser"))
@@ -311,7 +312,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetOwner_sendsAdminAndUpdatesLocalNode() {
- handler.start(testScope)
+ handler = createHandler(testScope)
val meshUser =
MeshUser(
id = "!12345678",
@@ -331,7 +332,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSend_sendsDataAndBroadcastsStatus() {
- handler.start(testScope)
+ handler = createHandler(testScope)
val packet = DataPacket(to = "!deadbeef", dataType = 1, bytes = null, channel = 0)
handler.handleSend(packet, MY_NODE_NUM)
@@ -345,7 +346,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestPosition_sameNode_doesNothing() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleRequestPosition(MY_NODE_NUM, Position(0.0, 0.0, 0), MY_NODE_NUM)
@@ -354,7 +355,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestPosition_provideLocation_validPosition_usesGivenPosition() {
- handler.start(testScope)
+ handler = createHandler(testScope)
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(true)
val validPosition = Position(37.7749, -122.4194, 10)
@@ -365,7 +366,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestPosition_provideLocation_invalidPosition_fallsBackToNodeDB() {
- handler.start(testScope)
+ handler = createHandler(testScope)
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(true)
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
@@ -378,7 +379,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestPosition_doNotProvide_sendsZeroPosition() {
- handler.start(testScope)
+ handler = createHandler(testScope)
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(false)
val validPosition = Position(37.7749, -122.4194, 10)
@@ -392,7 +393,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetConfig_decodesAndSendsAdmin_thenPersistsLocally() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
everySuspend { radioConfigRepository.setLocalConfig(any()) } returns Unit
val config = Config(lora = Config.LoRaConfig(hop_limit = 5))
@@ -409,7 +410,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetModuleConfig_ownNode_persistsLocally() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
myNodeNumFlow.value = MY_NODE_NUM
everySuspend { radioConfigRepository.setLocalModuleConfig(any()) } returns Unit
@@ -425,7 +426,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetModuleConfig_remoteNode_doesNotPersistLocally() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
myNodeNumFlow.value = MY_NODE_NUM
val moduleConfig = ModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
@@ -442,7 +443,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetChannel_nonNullPayload_decodesAndPersists() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
everySuspend { radioConfigRepository.updateChannelSettings(any()) } returns Unit
val channel = Channel(index = 1)
@@ -457,7 +458,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetChannel_nullPayload_doesNothing() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleSetChannel(null, MY_NODE_NUM)
@@ -468,7 +469,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRemoveByNodenum_removesAndSendsAdmin() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleRemoveByNodenum(REMOTE_NODE_NUM, 99, MY_NODE_NUM)
@@ -480,7 +481,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetRemoteOwner_decodesAndSendsAdmin() {
- handler.start(testScope)
+ handler = createHandler(testScope)
val user = User(id = "!remote01", long_name = "Remote", short_name = "RM")
val payload = User.ADAPTER.encode(user)
@@ -495,7 +496,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleGetRemoteConfig_sessionkeyConfig_sendsDeviceMetadataRequest() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleGetRemoteConfig(1, REMOTE_NODE_NUM, AdminMessage.ConfigType.SESSIONKEY_CONFIG.value)
@@ -504,7 +505,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleGetRemoteConfig_regularConfig_sendsConfigRequest() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleGetRemoteConfig(1, REMOTE_NODE_NUM, AdminMessage.ConfigType.LORA_CONFIG.value)
@@ -515,7 +516,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetRemoteChannel_nullPayload_doesNothing() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleSetRemoteChannel(1, REMOTE_NODE_NUM, null)
@@ -524,7 +525,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleSetRemoteChannel_nonNullPayload_decodesAndSendsAdmin() {
- handler.start(testScope)
+ handler = createHandler(testScope)
val channel = Channel(index = 2)
val payload = Channel.ADAPTER.encode(channel)
@@ -538,7 +539,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestRebootOta_withNullHash_sendsAdmin() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleRequestRebootOta(1, REMOTE_NODE_NUM, 0, null)
@@ -547,7 +548,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestRebootOta_withHash_sendsAdmin() {
- handler.start(testScope)
+ handler = createHandler(testScope)
val hash = byteArrayOf(0x01, 0x02, 0x03)
handler.handleRequestRebootOta(1, REMOTE_NODE_NUM, 1, hash)
@@ -559,7 +560,7 @@ class MeshActionHandlerImplTest {
@Test
fun handleRequestNodedbReset_sendsAdminWithPreserveFavorites() {
- handler.start(testScope)
+ handler = createHandler(testScope)
handler.handleRequestNodedbReset(1, REMOTE_NODE_NUM, preserveFavorites = true)

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt
index 9580d53631..e05c6f20a8 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt
@@ -98,8 +98,8 @@ class MeshConfigFlowManagerImplTest {
analytics = analytics,
commandSender = commandSender,
packetHandler = packetHandler,
+ scope = testScope,
)
- manager.start(testScope)
}
// ---------- handleMyInfo ----------

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt
index b71942d0e4..bf3247815b 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt
@@ -23,6 +23,7 @@ import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verifySuspend
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -60,20 +61,20 @@ class MeshConfigHandlerImplTest {
fun setUp() {
every { radioConfigRepository.localConfigFlow } returns localConfigFlow
every { radioConfigRepository.moduleConfigFlow } returns moduleConfigFlow
-
- handler =
- MeshConfigHandlerImpl(
- radioConfigRepository = radioConfigRepository,
- serviceRepository = serviceRepository,
- nodeManager = nodeManager,
- )
}
+ private fun createHandler(scope: CoroutineScope): MeshConfigHandlerImpl = MeshConfigHandlerImpl(
+ radioConfigRepository = radioConfigRepository,
+ serviceRepository = serviceRepository,
+ nodeManager = nodeManager,
+ scope = scope,
+ )
+
// ---------- start and flow wiring ----------
@Test
fun `start wires localConfig flow from repository`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val config = LocalConfig(device = Config.DeviceConfig(role = Config.DeviceConfig.Role.ROUTER))
localConfigFlow.value = config
advanceUntilIdle()
@@ -83,7 +84,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `start wires moduleConfig flow from repository`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val config = LocalModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
moduleConfigFlow.value = config
advanceUntilIdle()
@@ -95,7 +96,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleDeviceConfig persists config and updates progress`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val config = Config(device = Config.DeviceConfig(role = Config.DeviceConfig.Role.CLIENT))
handler.handleDeviceConfig(config)
advanceUntilIdle()
@@ -106,7 +107,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleDeviceConfig handles all config variants`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val configs =
listOf(
Config(position = Config.PositionConfig()),
@@ -131,7 +132,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleModuleConfig persists config and updates progress`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val config = ModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
handler.handleModuleConfig(config)
advanceUntilIdle()
@@ -142,7 +143,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleModuleConfig with statusmessage updates node status`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val myNum = 123
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(myNum)
@@ -155,7 +156,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleModuleConfig with statusmessage skipped when myNodeNum is null`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(null)
val config = ModuleConfig(statusmessage = ModuleConfig.StatusMessageConfig(node_status = "Active"))
@@ -168,7 +169,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleChannel persists channel settings`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val channel = Channel(index = 0)
handler.handleChannel(channel)
advanceUntilIdle()
@@ -178,7 +179,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleChannel shows progress with max channels when myNodeInfo available`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { nodeManager.getMyNodeInfo() } returns
MyNodeInfo(
myNodeNum = 123,
@@ -206,7 +207,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleChannel shows progress without max channels when myNodeInfo unavailable`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
every { nodeManager.getMyNodeInfo() } returns null
val channel = Channel(index = 0)
@@ -220,7 +221,7 @@ class MeshConfigHandlerImplTest {
@Test
fun `handleDeviceUIConfig persists config`() = runTest(testDispatcher) {
- handler.start(backgroundScope)
+ handler = createHandler(backgroundScope)
val config = DeviceUIConfig()
handler.handleDeviceUIConfig(config)
advanceUntilIdle()

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImplTest.kt
index 5263254d3f..36ee37f2ee 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImplTest.kt
@@ -24,7 +24,7 @@ import dev.mokkery.everySuspend
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -60,7 +60,7 @@ import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class MeshConnectionManagerImplTest {
private val radioInterfaceService = mock<RadioInterfaceService>(MockMode.autofill)
private val serviceRepository = mock<ServiceRepository>(MockMode.autofill)
@@ -108,29 +108,29 @@ class MeshConnectionManagerImplTest {
every { mqttManager.stop() } returns Unit
every { nodeManager.nodeDBbyNodeNum } returns emptyMap<Int, Node>()
every { packetHandler.sendToRadio(any<org.meshtastic.proto.ToRadio>()) } returns Unit
-
- manager =
- MeshConnectionManagerImpl(
- radioInterfaceService,
- serviceRepository,
- serviceBroadcasts,
- serviceNotifications,
- uiPrefs,
- packetHandler,
- nodeRepository,
- locationManager,
- mqttManager,
- historyManager,
- radioConfigRepository,
- commandSender,
- nodeManager,
- analytics,
- packetRepository,
- workerManager,
- appWidgetUpdater,
- )
}
+ private fun createManager(scope: CoroutineScope): MeshConnectionManagerImpl = MeshConnectionManagerImpl(
+ radioInterfaceService,
+ serviceRepository,
+ serviceBroadcasts,
+ serviceNotifications,
+ uiPrefs,
+ packetHandler,
+ nodeRepository,
+ locationManager,
+ mqttManager,
+ historyManager,
+ radioConfigRepository,
+ commandSender,
+ nodeManager,
+ analytics,
+ packetRepository,
+ workerManager,
+ appWidgetUpdater,
+ scope,
+ )
+
@AfterTest fun tearDown() {}
@Test
@@ -138,7 +138,7 @@ class MeshConnectionManagerImplTest {
every { packetHandler.sendToRadio(any<org.meshtastic.proto.ToRadio>()) } returns Unit
every { serviceNotifications.updateServiceStateNotification(any(), any()) } returns Unit
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
radioConnectionState.value = ConnectionState.Connected
advanceUntilIdle()
@@ -163,7 +163,7 @@ class MeshConnectionManagerImplTest {
every { locationManager.stop() } returns Unit
every { mqttManager.stop() } returns Unit
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
// Transition to Connected first so that Disconnected actually does something
radioConnectionState.value = ConnectionState.Connected
advanceUntilIdle()
@@ -197,7 +197,7 @@ class MeshConnectionManagerImplTest {
every { mqttManager.stop() } returns Unit
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
advanceUntilIdle()
radioConnectionState.value = ConnectionState.DeviceSleep
@@ -221,7 +221,7 @@ class MeshConnectionManagerImplTest {
every { locationManager.stop() } returns Unit
every { mqttManager.stop() } returns Unit
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
advanceUntilIdle()
radioConnectionState.value = ConnectionState.DeviceSleep
@@ -236,7 +236,7 @@ class MeshConnectionManagerImplTest {
@Test
fun `onRadioConfigLoaded enqueues queued packets and sets time`() = runTest(testDispatcher) {
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
val packetId = 456
everySuspend { packetRepository.getQueuedPackets() } returns listOf(dataPacket)
every { workerManager.enqueueSendMessage(any()) } returns Unit
@@ -257,15 +257,15 @@ class MeshConnectionManagerImplTest {
moduleConfigFlow.value = moduleConfig
every { commandSender.requestTelemetry(any(), any(), any()) } returns Unit
every { nodeManager.myNodeNum } returns MutableStateFlow(123)
- every { mqttManager.start(any(), any(), any()) } returns Unit
+ every { mqttManager.startProxy(any(), any()) } returns Unit
every { historyManager.requestHistoryReplay(any(), any(), any(), any()) } returns Unit
every { nodeManager.getMyNodeInfo() } returns null
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
manager.onNodeDbReady()
advanceUntilIdle()
- verify { mqttManager.start(any(), true, true) }
+ verify { mqttManager.startProxy(true, true) }
verify { historyManager.requestHistoryReplay(any(), any(), any(), any()) }
}
@@ -286,7 +286,7 @@ class MeshConnectionManagerImplTest {
every { mqttManager.stop() } returns Unit
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
- manager.start(backgroundScope)
+ manager = createManager(backgroundScope)
advanceUntilIdle()
// Transition to Connected then DeviceSleep

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt
index 5f738b439e..022608be19 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt
@@ -108,8 +108,8 @@ class MeshDataHandlerTest {
storeForwardHandler = storeForwardHandler,
telemetryHandler = telemetryHandler,
adminPacketHandler = adminPacketHandler,
+ scope = testScope,
)
- handler.start(testScope)
// Default: mapper returns null for empty packets, which is the safe default
every { dataMapper.toDataPacket(any()) } returns null

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
index 3090cf49ed..251aefabee 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
@@ -23,6 +23,7 @@ import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verifySuspend
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -65,22 +66,22 @@ class MeshMessageProcessorImplTest {
every { nodeManager.isNodeDbReady } returns isNodeDbReady
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(myNodeNum)
every { router.dataHandler } returns dataHandler
-
- processor =
- MeshMessageProcessorImpl(
- nodeManager = nodeManager,
- serviceRepository = serviceRepository,
- meshLogRepository = lazy { meshLogRepository },
- router = lazy { router },
- fromRadioDispatcher = fromRadioDispatcher,
- )
}
+ private fun createProcessor(scope: CoroutineScope): MeshMessageProcessorImpl = MeshMessageProcessorImpl(
+ nodeManager = nodeManager,
+ serviceRepository = serviceRepository,
+ meshLogRepository = lazy { meshLogRepository },
+ router = lazy { router },
+ fromRadioDispatcher = fromRadioDispatcher,
+ scope = scope,
+ )
+
// ---------- handleFromRadio: non-packet variants ----------
@Test
fun `handleFromRadio dispatches non-packet variants to fromRadioDispatcher`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
val logRecord = LogRecord(message = "test log")
val fromRadio = FromRadio(log_record = logRecord)
val bytes = FromRadio.ADAPTER.encode(fromRadio)
@@ -93,7 +94,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `handleFromRadio falls back to LogRecord parsing when FromRadio fails`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
// Encode a raw LogRecord (not wrapped in FromRadio) — first decode as FromRadio fails,
// fallback decode as LogRecord succeeds
val logRecord = LogRecord(message = "fallback log")
@@ -108,7 +109,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `handleFromRadio with completely invalid bytes does not crash`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
// Invalid protobuf bytes — both parses should fail
val garbage = byteArrayOf(0xFF.toByte(), 0xFE.toByte(), 0xFD.toByte())
@@ -121,7 +122,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `packets are buffered when node DB is not ready`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = false
val packet =
@@ -141,7 +142,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `buffered packets are flushed when node DB becomes ready`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = false
val packet =
@@ -165,7 +166,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `early buffer overflow drops oldest packet`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = false
// The maxEarlyPacketBuffer is 10240 — we won't actually fill it in this test,
@@ -195,7 +196,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `packets with rx_time 0 get current time`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val packet =
@@ -214,7 +215,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `packets with non-zero rx_time keep their time`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val packet =
@@ -235,7 +236,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `processReceivedMeshPacket updates myNode lastHeard`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val packet =
@@ -255,7 +256,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `processReceivedMeshPacket updates sender node`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val senderNode = 999
@@ -279,7 +280,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `packet with null decoded is skipped`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val packet = MeshPacket(id = 1, from = 999, decoded = null)
@@ -293,7 +294,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `processReceivedMeshPacket with null myNodeNum skips node updates`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = true
val packet =
@@ -315,7 +316,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `clearEarlyPackets empties the buffer`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
isNodeDbReady.value = false
val packet =
@@ -342,7 +343,7 @@ class MeshMessageProcessorImplTest {
@Test
fun `FromRadio log_record variant is logged as MeshLog`() = runTest(testDispatcher) {
- processor.start(backgroundScope)
+ processor = createProcessor(backgroundScope)
val logRecord = LogRecord(message = "device log")
val fromRadio = FromRadio(log_record = logRecord)
val bytes = FromRadio.ADAPTER.encode(fromRadio)

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt
index 0225904679..5090668672 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt
@@ -18,6 +18,7 @@ package org.meshtastic.core.data.manager
import dev.mokkery.MockMode
import dev.mokkery.mock
+import kotlinx.coroutines.test.TestScope
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.meshtastic.core.model.DataPacket
@@ -44,12 +45,13 @@ class NodeManagerImplTest {
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
private val serviceBroadcasts: ServiceBroadcasts = mock(MockMode.autofill)
private val notificationManager: NotificationManager = mock(MockMode.autofill)
+ private val testScope = TestScope()
private lateinit var nodeManager: NodeManagerImpl
@BeforeTest
fun setUp() {
- nodeManager = NodeManagerImpl(nodeRepository, serviceBroadcasts, notificationManager)
+ nodeManager = NodeManagerImpl(nodeRepository, serviceBroadcasts, notificationManager, testScope)
}
@Test

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/PacketHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/PacketHandlerImplTest.kt
index fe89063efe..0a1698c9a8 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/PacketHandlerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/PacketHandlerImplTest.kt
@@ -70,8 +70,8 @@ class PacketHandlerImplTest {
radioInterfaceService,
lazy { meshLogRepository },
serviceRepository,
+ testScope,
)
- handler.start(testScope)
}
@Test

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt
index e465aaa63d..900245332f 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt
@@ -72,8 +72,8 @@ class StoreForwardPacketHandlerImplTest {
serviceBroadcasts = serviceBroadcasts,
historyManager = historyManager,
dataHandler = lazy { dataHandler },
+ scope = testScope,
)
- handler.start(testScope)
}
private fun makeSfPacket(from: Int, sf: StoreAndForward): MeshPacket {

diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt
index 8f295a2b67..28bf22fdcc 100644
--- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt
@@ -62,8 +62,8 @@ class TelemetryPacketHandlerImplTest {
nodeManager = nodeManager,
connectionManager = lazy { connectionManager },
notificationManager = notificationManager,
+ scope = testScope,
)
- handler.start(testScope)
}
private fun makeTelemetryPacket(from: Int, telemetry: Telemetry): MeshPacket {

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/CommandSender.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/CommandSender.kt
index 2b897baa98..b99a002de8 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/CommandSender.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/CommandSender.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import okio.ByteString
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Position
@@ -27,9 +26,6 @@ import org.meshtastic.proto.LocalConfig
/** Interface for sending commands and packets to the mesh network. */
@Suppress("TooManyFunctions")
interface CommandSender {
- /** Starts the command sender with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Returns the current packet ID. */
fun getCurrentPacketId(): Long

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshActionHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshActionHandler.kt
index ac92e82878..5c43efdcd0 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshActionHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshActionHandler.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MeshUser
import org.meshtastic.core.model.Position
@@ -25,9 +24,6 @@ import org.meshtastic.core.model.service.ServiceAction
/** Interface for handling UI-triggered actions and administrative commands for the mesh. */
@Suppress("TooManyFunctions")
interface MeshActionHandler {
- /** Starts the handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Processes a service action from the UI. */
suspend fun onServiceAction(action: ServiceAction)

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.kt
index 2a92f8909a..b2bb6d4187 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.DeviceMetadata
import org.meshtastic.proto.FileInfo
import org.meshtastic.proto.MyNodeInfo
@@ -24,9 +23,6 @@ import org.meshtastic.proto.NodeInfo
/** Interface for managing the configuration flow, including local node info and metadata. */
interface MeshConfigFlowManager {
- /** Starts the manager with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Handles received local node information. */
fun handleMyInfo(myInfo: MyNodeInfo)

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt
index 3f3887631b..c0e60337e5 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.proto.Channel
import org.meshtastic.proto.Config
@@ -27,9 +26,6 @@ import org.meshtastic.proto.ModuleConfig
/** Interface for handling device and module configuration updates. */
interface MeshConfigHandler {
- /** Starts the handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Reactive local configuration. */
val localConfig: StateFlow<LocalConfig>

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt
index eae5bd9a0d..c60db9afa2 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt
@@ -16,14 +16,10 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.Telemetry
/** Interface for managing the connection lifecycle and status with the mesh radio. */
interface MeshConnectionManager {
- /** Starts the connection manager with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Called when the radio configuration has been fully loaded. */
fun onRadioConfigLoaded()

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt
index 2c7487cf99..7d5f2a913c 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt
@@ -16,16 +16,12 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import org.meshtastic.core.model.DataPacket
import org.meshtastic.proto.MeshPacket
/** Interface for handling incoming mesh data packets and routing them to the appropriate handlers. */
interface MeshDataHandler {
- /** Starts the handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/**
* Processes a received mesh packet.
*

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt
index 1a3657d9e1..a8d6545cee 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt
@@ -16,14 +16,10 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
/** Interface for processing incoming radio messages and mesh packets. */
interface MeshMessageProcessor {
- /** Starts the processor with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Handles a raw message received from the radio. */
fun handleFromRadio(bytes: ByteArray, myNodeNum: Int?)

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshRouter.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshRouter.kt
index be2830af9c..42b306b178 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshRouter.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshRouter.kt
@@ -16,13 +16,8 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
-
/** Interface for the central router that orchestrates specialized mesh packet handlers. */
interface MeshRouter {
- /** Starts the router and its sub-components with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Access to the data handler. */
val dataHandler: MeshDataHandler

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MqttManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MqttManager.kt
index cfda5a9d09..7ebfa05219 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MqttManager.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MqttManager.kt
@@ -16,13 +16,12 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MqttClientProxyMessage
/** Interface for managing MQTT proxy communication. */
interface MqttManager {
- /** Starts the MQTT manager with the given coroutine scope and settings. */
- fun start(scope: CoroutineScope, enabled: Boolean, proxyToClientEnabled: Boolean)
+ /** Starts the MQTT proxy with the given settings. */
+ fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean)
/** Stops the MQTT manager. */
fun stop()

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt
index b9759ff596..9031463317 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt
@@ -16,15 +16,11 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.NeighborInfo
/** Interface for handling neighbor info responses from the mesh. */
interface NeighborInfoHandler {
- /** Starts the neighbor info handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Records the start time for a neighbor info request. */
fun recordStartTime(requestId: Int)

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt
index a0d1153919..ac67185728 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt
@@ -16,7 +16,6 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
@@ -51,9 +50,6 @@ interface NodeManager : NodeIdLookup {
/** Sets whether node database writes are allowed. */
fun setAllowNodeDbWrites(allowed: Boolean)
- /** Starts the node manager with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** The local node number as a thread-safe [StateFlow]. */
val myNodeNum: StateFlow<Int?>

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.kt
index 686840f409..081e2928ba 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.kt
@@ -16,16 +16,12 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.QueueStatus
import org.meshtastic.proto.ToRadio
/** Interface for handling the transmission of packets to the radio and managing the packet queue. */
interface PacketHandler {
- /** Starts the packet handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Sends a command/packet directly to the radio. */
fun sendToRadio(p: ToRadio)

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt
index 51006763d6..bda122ac1a 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt
@@ -16,15 +16,11 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.core.model.DataPacket
import org.meshtastic.proto.MeshPacket
/** Interface for handling Store & Forward (legacy) and SF++ packets. */
interface StoreForwardPacketHandler {
- /** Starts the handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/**
* Handles a legacy Store & Forward packet.
*

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt
index a53cd8b8a4..b1f1aa2c9b 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt
@@ -16,15 +16,11 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import org.meshtastic.core.model.DataPacket
import org.meshtastic.proto.MeshPacket
/** Interface for handling telemetry packets from the mesh, including battery notifications. */
interface TelemetryPacketHandler {
- /** Starts the handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/**
* Processes a telemetry packet.
*

diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt
index aa2e6318a5..6535ef30cc 100644
--- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt
+++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt
@@ -16,15 +16,11 @@
*/
package org.meshtastic.core.repository
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import org.meshtastic.proto.MeshPacket
/** Interface for handling traceroute responses from the mesh. */
interface TracerouteHandler {
- /** Starts the traceroute handler with the given coroutine scope. */
- fun start(scope: CoroutineScope)
-
/** Records the start time for a traceroute request. */
fun recordStartTime(requestId: Int)

diff --git a/core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt b/core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
index 7e9832b544..e651d95cec 100644
--- a/core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
+++ b/core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
@@ -22,16 +22,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.common.database.DatabaseManager
import org.meshtastic.core.common.util.handledLaunch
-import org.meshtastic.core.repository.CommandSender
-import org.meshtastic.core.repository.MeshConnectionManager
import org.meshtastic.core.repository.MeshMessageProcessor
import org.meshtastic.core.repository.MeshRouter
import org.meshtastic.core.repository.MeshServiceNotifications
import org.meshtastic.core.repository.NodeManager
-import org.meshtastic.core.repository.PacketHandler
import org.meshtastic.core.repository.RadioInterfaceService
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.repository.TakPrefs
@@ -51,25 +49,22 @@ import org.meshtastic.core.takserver.TAKServerManager
class MeshServiceOrchestrator(
private val radioInterfaceService: RadioInterfaceService,
private val serviceRepository: ServiceRepository,
- private val packetHandler: PacketHandler,
private val nodeManager: NodeManager,
private val messageProcessor: MeshMessageProcessor,
- private val commandSender: CommandSender,
- private val connectionManager: MeshConnectionManager,
private val router: MeshRouter,
private val serviceNotifications: MeshServiceNotifications,
private val takServerManager: TAKServerManager,
private val takMeshIntegration: TAKMeshIntegration,
private val takPrefs: TakPrefs,
- private val dispatchers: org.meshtastic.core.di.CoroutineDispatchers,
private val databaseManager: DatabaseManager,
+ @Named("ServiceScope") private val scope: CoroutineScope,
) {
private var serviceJob: Job? = null
private var takJob: Job? = null
- /** The coroutine scope for the service. Available after [start] is called. */
- var serviceScope: CoroutineScope? = null
- private set
+ /** The coroutine scope for the service. */
+ val serviceScope: CoroutineScope
+ get() = scope
/** Whether the orchestrator is currently running. */
val isRunning: Boolean
@@ -78,8 +73,8 @@ class MeshServiceOrchestrator(
/**
* Starts the mesh service components and wires up data flows.
*
- * This is the KMP equivalent of `MeshService.onCreate()`. It starts all managers, connects to the radio, and wires
- * incoming radio data to the message processor and service actions to the router's action handler.
+ * This is the KMP equivalent of `MeshService.onCreate()`. It connects to the radio and wires incoming radio data to
+ * the message processor and service actions to the router's action handler.
*/
fun start() {
if (isRunning) {
@@ -90,18 +85,9 @@ class MeshServiceOrchestrator(
Logger.i { "Starting mesh service orchestrator" }
val job = Job()
serviceJob = job
- val scope = CoroutineScope(dispatchers.default + job)
- serviceScope = scope
serviceNotifications.initChannels()
- packetHandler.start(scope)
- router.start(scope)
- nodeManager.start(scope)
- connectionManager.start(scope)
- messageProcessor.start(scope)
- commandSender.start(scope)
-
// Observe TAK server pref to start/stop
takJob =
takPrefs.isTakServerEnabled
@@ -161,6 +147,5 @@ class MeshServiceOrchestrator(
}
serviceJob?.cancel()
serviceJob = null
- serviceScope = null
}
}

diff --git a/core/service/src/commonMain/kotlin/org/meshtastic/core/service/di/CoreServiceModule.kt b/core/service/src/commonMain/kotlin/org/meshtastic/core/service/di/CoreServiceModule.kt
index d007f1ea3d..3fae4287b4 100644
--- a/core/service/src/commonMain/kotlin/org/meshtastic/core/service/di/CoreServiceModule.kt
+++ b/core/service/src/commonMain/kotlin/org/meshtastic/core/service/di/CoreServiceModule.kt
@@ -16,9 +16,19 @@
*/
package org.meshtastic.core.service.di
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
+import org.koin.core.annotation.Named
+import org.koin.core.annotation.Single
+import org.meshtastic.core.di.CoroutineDispatchers
@Module
@ComponentScan("org.meshtastic.core.service")
-class CoreServiceModule
+class CoreServiceModule {
+ @Single
+ @Named("ServiceScope")
+ fun provideServiceScope(dispatchers: CoroutineDispatchers): CoroutineScope =
+ CoroutineScope(dispatchers.default + SupervisorJob())
+}

diff --git a/core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt b/core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
index 611454d05a..48be7dbf6f 100644
--- a/core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
+++ b/core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
@@ -25,23 +25,21 @@ import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verify.VerifyMode.Companion.exactly
import dev.mokkery.verifySuspend
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.meshtastic.core.common.database.DatabaseManager
-import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.service.ServiceAction
import org.meshtastic.core.repository.CommandSender
import org.meshtastic.core.repository.MeshActionHandler
import org.meshtastic.core.repository.MeshConfigHandler
-import org.meshtastic.core.repository.MeshConnectionManager
import org.meshtastic.core.repository.MeshMessageProcessor
import org.meshtastic.core.repository.MeshRouter
import org.meshtastic.core.repository.MeshServiceNotifications
import org.meshtastic.core.repository.NodeManager
import org.meshtastic.core.repository.NodeRepository
-import org.meshtastic.core.repository.PacketHandler
import org.meshtastic.core.repository.RadioInterfaceService
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.repository.TakPrefs
@@ -57,12 +55,10 @@ class MeshServiceOrchestratorTest {
private val radioInterfaceService: RadioInterfaceService = mock(MockMode.autofill)
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
- private val packetHandler: PacketHandler = mock(MockMode.autofill)
private val nodeManager: NodeManager = mock(MockMode.autofill)
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
private val messageProcessor: MeshMessageProcessor = mock(MockMode.autofill)
private val commandSender: CommandSender = mock(MockMode.autofill)
- private val connectionManager: MeshConnectionManager = mock(MockMode.autofill)
private val router: MeshRouter = mock(MockMode.autofill)
private val actionHandler: MeshActionHandler = mock(MockMode.autofill)
private val meshConfigHandler: MeshConfigHandler = mock(MockMode.autofill)
@@ -73,7 +69,7 @@ class MeshServiceOrchestratorTest {
private val databaseManager: DatabaseManager = mock(MockMode.autofill)
private val testDispatcher = UnconfinedTestDispatcher()
- private val dispatchers = CoroutineDispatchers(testDispatcher, testDispatcher, testDispatcher)
+ private val testScope = CoroutineScope(testDispatcher)
/** Stubs the shared flow dependencies used by every test and returns an orchestrator. */
private fun createOrchestrator(
@@ -107,18 +103,15 @@ class MeshServiceOrchestratorTest {
return MeshServiceOrchestrator(
radioInterfaceService = radioInterfaceService,
serviceRepository = serviceRepository,
- packetHandler = packetHandler,
nodeManager = nodeManager,
messageProcessor = messageProcessor,
- commandSender = commandSender,
- connectionManager = connectionManager,
router = router,
serviceNotifications = serviceNotifications,
takServerManager = takServerManager,
takMeshIntegration = takMeshIntegration,
takPrefs = takPrefs,
- dispatchers = dispatchers,
databaseManager = databaseManager,
+ scope = testScope,
)
}
@@ -131,7 +124,6 @@ class MeshServiceOrchestratorTest {
assertTrue(orchestrator.isRunning)
verify { serviceNotifications.initChannels() }
- verify { packetHandler.start(any()) }
verify { nodeManager.loadCachedNodeDB() }
orchestrator.stop()
@@ -217,7 +209,6 @@ class MeshServiceOrchestratorTest {
// Components should only be initialized once
verify(exactly(1)) { serviceNotifications.initChannels() }
- verify(exactly(1)) { packetHandler.start(any()) }
verify(exactly(1)) { nodeManager.loadCachedNodeDB() }
orchestrator.stop()

Served by rngit 1.5.2 - Generated in 0.67s